/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "SceneObject.h"

#include "GameObject.h"
#include "EntityObject.h"
#include "LightObject.h"
#include "CameraObject.h"
#include "TerrainGroupObject.h"

#include "GameEngine.h"
#include "GPKMeshLoadImpl.h"
#include "ProjectResourceManager.h"
#include "GameEventManager.h"
#include "GameObjectManager.h"
#include "TerrainManager.h"
#include "SpriteManager.h"
#include "PrimitiveManager.h"
#include "SkyManager.h"

namespace PQEngine
{
	const std::string SceneResourceGroup::FILE_SYSTEM="FileSystem";
	const std::string SceneResourceGroup::ZIP="Zip";

	const Ogre::String SceneResourceGroupNames::RESOURCE_GROUP_NAME_ENVIRONMENT="ResourceGroup/Environment";
	const Ogre::String RESOURCE_GROUP_NAME_TERRAIN="ResourceGroup/Terrain";

	SceneResourceGroup::SceneResourceGroup()
		: _count(0)
	{
	}

	SceneResourceGroup& SceneResourceGroup::getSingleton()
	{
		static SceneResourceGroup inst;
		return inst;
	}

	bool SceneResourceGroup::add(std::string path)
	{
		if(!legal(path)){
			return false;
		}
		if(!exist(path)){
			ResourceGroupType type=getType(path);
			std::string groupName=genName();
			Ogre::ResourceGroupManager::getSingleton().addResourceLocation(path,getGroupLocType(type),groupName);
			Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup(groupName);
			_lists[type].push_back(path);
		}
		return true;
	}

	

	std::string SceneResourceGroup::getGroupLocType(ResourceGroupType type)
	{
		if(type==RES_GROUP_ZIP){
			return ZIP;
		}
		return FILE_SYSTEM;
	}

	SceneResourceGroup::ResourceGroupType SceneResourceGroup::getType(std::string path)
	{
		int pos=path.find_last_of(".");
		std::string ext=path.substr(pos+1);

		if(ext=="zip"||ext=="Zip"||ext=="ZIP"){
			return RES_GROUP_ZIP;
		}
		return RES_GROUP_FILE_SYSTEM;
	}


	std::string SceneResourceGroup::genName()
	{
		std::string groupName="ResourceGroup"+Ogre::StringConverter::toString(_count);
		_count++;
		return groupName;
	}

	bool SceneResourceGroup::exist(std::string path)
	{
		ResourceGroupType type= SceneResourceGroup::getType(path);
		int size=_lists[type].size();
		for(int i=0;i<size;i++){
			if(_lists[type].at(i)==path){
				return true;
			}
		}
		return false;
	}
	
	bool SceneResourceGroup::legal(std::string path)
	{
		return true;
	}

	SceneResourceLoader::SceneResourceLoader(SceneObject* sceneObject, std::string path,SceneResourceLocation loc)
		: _sceneObject(sceneObject), _path(path), _loc(loc),_impl(0)
	{
		assert(_sceneObject);

		_type=getResourceType(getExt());
	}

	SceneResourceLoader::~SceneResourceLoader()
	{
		if(_impl){
			delete _impl;
		}
	}


	GameObject* SceneResourceLoader::load()
	{
		switch(_loc){
			case LOC_FILE_SYSTEM:
				return loadFromLocalFS();
			case LOC_MPQ_ARCHIVE:
				return loadFromMPQ();
			case LOC_GPACK_ARCHIVE:
				return loadFromGPK();
			default:
				break;

		}

		return 0;
	}

	GameObject* SceneResourceLoader::loadFromLocalFS()
	{
		switch(_type){
			case SCENE_RESOURCE_MESH:
				return loadMesh();
			case SCENE_RESOURCE_FILE_BLEND:
				return loadBlend();
			default:
				break;
		}

		return 0;

	}

	GameObject* SceneResourceLoader::loadFromMPQ()
	{
		switch(_type){
			case SCENE_RESOURCE_M2_MODLE:
				return loadM2();
			default:
				break;
		}
		return 0;

	}

	GameObject* SceneResourceLoader::loadFromGPK()
	{
		switch(_type){
			case SCENE_RESOURCE_MESH:
				return loadMeshFromGPK();
			default:
				break;
		}
		return 0;
	}

	GameObject* SceneResourceLoader::loadMeshFromGPK()
	{
		_impl=new GPKMeshLoadImpl(_sceneObject,_path,_loc);
		GameObject* obj= _impl->load();

		return obj;

	}

	//load mesh into ogre as entity
	GameObject* SceneResourceLoader::loadMesh()
	{
		int pos=_path.find_last_of("/");
		std::string parent=_path.substr(0,pos);
		SceneResourceGroup::getSingleton().add(parent);

		GameObject* object= _sceneObject->createEntityObject(getName());
		return object;
	}


	GameObject* SceneResourceLoader::loadM2()
	{
		
		assert(_impl);
		return 0;
		/*
		_impl=new M2LoadImpl(_sceneObject,_path,_loc);
		return _impl->load();
		*/
	}

	GameObject* SceneResourceLoader::loadBlend()
	{
		assert(_impl);
		return 0;
		/*
		_impl=new BlenderLoadImpl(_sceneObject,_path);
		return _impl->load();
		*/
	}

	SceneRenderProperty::SceneRenderProperty(SceneObject* obj)
		: _backgroundColor(0.5f, 0.5f, 0.5f),_ambient(0.5f,0.5f,0.5f),_fogColor(),
		_fogDensity(0.0f),_mode(Ogre::PM_SOLID),_sceneObject(obj)
	{
		assert(_sceneObject);
	}

	void SceneRenderProperty::setBackground(Ogre::ColourValue color)
	{
		_backgroundColor=color;
		_sceneObject->setBackground(_backgroundColor);
	}

	void SceneRenderProperty::setAmbient(Ogre::ColourValue color)
	{
		_ambient=color;
		_sceneObject->setAmbient(_backgroundColor);
	}

	void SceneRenderProperty::setPolyMode(Ogre::PolygonMode mode)
	{
		_mode=mode;
		_sceneObject->setPolyMode(_mode);
		
	}

	SceneObject *SceneObject::_activeScene=0;

	SceneObject::SceneObject(Ogre::String hWnd,int width,int height)
		: Object(OBJECT_TYPE_SCENE_OBJECT),_rootObject(0),_skyManager(0),_terrainManager(0),
		_window(0),_camera(0),_viewport(0),_isActivate(false),_spriteManager(0),
		_selectedObject(0),_sceneManager(0),_renderProperty(0),_mouseX(0),_mouseY(0)
	{
		Ogre::NameValuePairList params;
		params["externalWindowHandle"] = hWnd;

		_window = Ogre::Root::getSingleton().createRenderWindow("RenderWindow"+getId(),width,height,false,&params);
		_renderProperty=new SceneRenderProperty(this);

		
		initDefaultScene();

	}

	void SceneObject::initDefaultScene()
	{
		_sceneManager = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC,"SceneManager"+getId());
		
		//create root game object
		_rootObject=new GameObject(this,GAME_OBJECT_ROOT);
		_rootObject->setName("RootObject");

		_camera=new CameraObject(this);
		_camera->setFarClipDistance(10000);
		_camera->setNearClipDistance(1);

		
		_sceneManager->setAmbientLight(_renderProperty->ambient());//render settings

		
		_spriteManager=new SpriteManager(this);
		
		_selectMeshData=new MeshData();

		// Create RaySceneQuery for select object
		_raySceneQuery = _sceneManager->createRayQuery(Ogre::Ray());
		_raySceneQuery->setQueryTypeMask(Ogre::SceneManager::ENTITY_TYPE_MASK);
		_raySceneQuery->setSortByDistance(true);

		
		//_environmentManager=new EnvironmentManager(this);
		//_terrainManager=new TerrainManager(this);
		//_skyManager=new SkyManager(this);
		
	}

	void SceneObject::enableSkyDome(bool enabled)
	{
		if(_skyManager){
			_skyManager->enableSkyDome(enabled);
		}
	}


	SceneObject::~SceneObject()
	{
		if(_renderProperty){
			delete _renderProperty;
		}
	}

	GameObject* SceneObject::getRootGameObject()
	{
		return _rootObject;
	}
	
	Ogre::Ray SceneObject::getRay(int x,int y)
	{
		return _camera->getRay(x,y);
	}

	Ogre::Ray SceneObject::getRay()
	{
		return _camera->getRay(0.5f,0.5f);
	}

	GameObject* SceneObject::selectObject(int x,int y)
	{
		if(_selectedObject){
			_selectedObject->selectObject(false);//becarefull select and select object
			if(_selectedObject->isTerrain()){
				_terrainManager->deselectTerrain();
			}
		}

		Ogre::Ray ray = getRay(x,y);
		_selectedObject= _terrainManager->selectTerrain(x,y);
		GameObject* obj= getObject(ray);
		if(obj){//if have object selected.
			//override terrain object by normal game object.
			//because the normal game object is prefer than terrain
			_selectedObject=obj;
			_terrainManager->deselectTerrain();
		}

		if(_selectedObject){
			_selectedObject->selectObject(true);
		}

		return _selectedObject;
	}

	GameObject* SceneObject::getObject(Ogre::Ray ray)
	{
		GameObject* obj=0;
        _raySceneQuery->setRay(ray);
		//OGRE sort query by distance already
		_raySceneQuery->setSortByDistance(true);
        // Execute query
		Ogre::RaySceneQueryResult &result = _raySceneQuery->execute();
        Ogre::RaySceneQueryResult::iterator itr = result.begin();
		
		 // loop through the results
		for(; itr!=result.end(); ++itr){
			 if (itr->movable){
				 Ogre::SceneNode* node= itr->movable->getParentSceneNode();
				 if(itr->movable->getMovableType().compare("Entity")==0){
					Ogre::Entity* entity=static_cast<Ogre::Entity*>(itr->movable);

					_selectMeshData->loadMeshEntity(entity,node);
					if(_selectMeshData->intersect(ray)){
						obj=GameObject::get(node);
						if(obj){//find and return
							  return obj;
						}
					}
				}
			 }
		 }
		
		return obj;
	}

	GameObject* SceneObject::getObject(int x,int y)
	{
		//Just get object not select it
		Ogre::Ray ray =getRay(x,y);
		GameObject* obj= getObject(ray);
		if(!obj){
			//obj=_terrainManager->getTerrain(x,y);
			return obj;
		}
		return obj;
	}

	GameObject* SceneObject::selectObject(GameObject* object)
	{
		if(_selectedObject){
			_selectedObject->selectObject(false);		
		}

		_selectedObject=object;
		
		if(!object->isTerrain()){
			_terrainManager->deselectTerrain();
		}

		if(_selectedObject){
			_selectedObject->selectObject(true);
		}
		
		return _selectedObject;
	}


	GameObject* SceneObject::selectObject(int id)
	{
		if(_selectedObject){
			_selectedObject->selectObject(false);		
		}
		_selectedObject=0;

		Object* obj=Object::getObject(id);
		if(Object::isGameObject(obj)){
			_selectedObject=(GameObject*)obj;
			_selectedObject->selectObject(true);
			if(!_selectedObject->isTerrain()){
				_terrainManager->deselectTerrain();
			}
			return _selectedObject;
		}
		
		return _selectedObject;
	}

	GameObject* SceneObject::getGameObject()
	{
		return _selectedObject;
	}

	SceneObject* SceneObject::create(Ogre::String hWnd,int width,int height)
	{
		if(!Ogre::Root::getSingletonPtr()){
			//OGRE Root must created
			OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR,
					"failed to create object",
					"SceneObject::create");
		}

		SceneObject* object=new SceneObject(hWnd,width,height);
		if(_activeScene){
			_activeScene->setIsActivate(false);
		}
		object->setIsActivate(true);
		_activeScene=object;
		return object;

	}

	EntityObject *SceneObject::createEntityObject(Ogre::String meshName)
	{
		EntityObject* object=new EntityObject(this,meshName);
		return object;
	}
	
	Ogre::SceneNode* SceneObject::createCameraFollowSceneNode(const Ogre::String& name, const Ogre::Vector3& translate, const Ogre::Quaternion& rotate)
	{
		Ogre::SceneNode* node=_camera->getSceneNode()->createChildSceneNode(name,translate,rotate);
		return node;
	}

	Ogre::SceneNode* SceneObject::createCameraFollowSceneNode(const Ogre::Vector3& translate, const Ogre::Quaternion& rotate)
	{
		Ogre::SceneNode* node=_camera->getSceneNode()->createChildSceneNode(translate,rotate);
		return node;
	}

	LightObject *SceneObject::createLightObject(int lightType)
	{
		LightObject* object=new LightObject(this,(LightType)lightType);		
		return object;
	}


	void SceneObject::update(float deltaT)
	{
		if(_rootObject){
			_rootObject->update(deltaT);
		}
		if(_terrainManager){
			_terrainManager->update(deltaT);
		}
		if(_skyManager){
			_skyManager->update(deltaT);
		}
	}

	GameObject* SceneObject::addResource(std::string path)
	{
		SceneResourceLoader loader(this,path);
		GameObject* obj= loader.load();
		obj->setPosition(getRay().getPoint(GameEngine::OBJECT_FRAME_DISTANCE));
		selectObject(obj);
		return obj;
	}

	GameObject* SceneObject::addGPKResource(std::string path)
	{
		SceneResourceLoader loader(this,path,LOC_GPACK_ARCHIVE);
		GameObject* obj= loader.load();
		selectObject(obj);
		return obj;
	}
	

	void SceneObject::addCharacterModel(std::string path)
	{
		SceneResourceLoader loader(this,path,LOC_MPQ_ARCHIVE);
		loader.load();
	}

	void SceneObject::add2DSprite(Sprite2D* sprite)
	{
		if(_spriteManager){
			_spriteManager->addSprite(sprite);
		}
	}

	TerrainObject* SceneObject::createFlatTerrain(int slotX,int slotY,int diffuseTexIndex)
	{
		TerrainObject* obj=_terrainManager->createFlatTerrain(slotX,slotY,diffuseTexIndex);
		_selectedObject=obj;
		return obj;
	}

	void SceneObject::startEditTerrain()
	{
		if(_terrainManager){
			_terrainManager->startEdit();
		}
	}

	void SceneObject::stopEditTerrain()
	{
		if(_terrainManager){
			_terrainManager->stopEdit();
		}
	}

	void SceneObject::addTerrainLayer(std::string textureName)
	{
		if(_terrainManager){
			_terrainManager->addTerrainLayer(textureName);
		}
	}

	void SceneObject::selectTerrainLayer(int index)
	{
		if(_terrainManager){
			_terrainManager->selectLayer(index);
		}
	}

	void SceneObject::setTerrainEditMode(TerrainEditMode mode)
	{
		if(_terrainManager){
			
			_terrainManager->setEditMode(mode);
		}
	}

	TerrainEditMode SceneObject::getTerrainEditMode()
	{
		if(_terrainManager){
			return _terrainManager->getEditMode();
		}
		return TERRAIN_EDIT_NONE;
	}


	void SceneObject::selectTerrainBrush(int index)
	{
		if(_terrainManager){
			_terrainManager->selectTerrainBrush(index);
		}
	}

	int SceneObject::getNumTerrainTexture()
	{
		if(_terrainManager){
			return _terrainManager->getNumTerrainTexture();
		}
		return 0;
	}

	int SceneObject::getNumTerrainBrushTexture()
	{
		if(_terrainManager){
			return _terrainManager->getNumTerrainBrushTexture();
		}
		return 0;
	}


	Ogre::String SceneObject::getTerrainTextureName(int index)
	{
		if(_terrainManager){
			return _terrainManager->getTerrainTextureName(index);
		}

		return "";
	}

	Ogre::String SceneObject::getTerrainBrushTextureName(int index)
	{
		if(_terrainManager){
			return _terrainManager->getTerrainBrushTextureName(index);
		}

		return "";
	}
	

	GPKFile* SceneObject::getTerrainTextureFile(int index)
	{
		if(_terrainManager){
			return _terrainManager->getTerrainTextureFile(index);
		}

		return 0;
	}

	GPKFile* SceneObject::getTerrainBrushTextureFile(int index)
	{
		if(_terrainManager){
			return _terrainManager->getTerrainBrushTextureFile(index);
		}

		return 0;
	}
	

	GPKFile* SceneObject::getTerrainTextureFile(std::string name)
	{
		if(_terrainManager){
			return _terrainManager->getTerrainTextureFile(name);
		}

		return 0;
	}

	GPKFile* SceneObject::getTerrainBrushTextureFile(std::string name)
	{
		if(_terrainManager){
			return _terrainManager->getTerrainBrushTextureFile(name);
		}

		return 0;
	}


	TerrainGroupObject* SceneObject::getTerrainGroup()
	{
		if(_terrainManager){
			return _terrainManager->getTerrainGroup();
		}
		return 0;
	}

	TerrainObject* SceneObject::selectTerrain(int index)
	{
		TerrainObject* obj=_terrainManager->selectTerrain(index);
		_selectedObject=obj;
		return obj;
	}

	bool SceneObject::addTerrainTextureFromLocalFS(std::string filePath)
	{
		if(_terrainManager){
			return _terrainManager->addTextureFromLocalFS(filePath);
		}
		return false;
	}

	bool SceneObject::addTerrainBrushTextureFromLocalFS(std::string filePath)
	{
		if(_terrainManager){
			return _terrainManager->addBrushTextureFromLocalFS(filePath);
		}
		return false;
	}

	void SceneObject::createSphereObject(Ogre::Vector3 position)
	{

		Ogre::String meshName=SPrimitiveManager.getSphereMeshName();

		EntityObject* object= createEntityObject(meshName);
		object->setPosition(position);
		
	}

	
	Ogre::Mesh* SceneObject::createMesh(const Ogre::String &name,Ogre::ManualResourceLoader *loader)
	{
		assert(loader);//must provide user implemented manual resource loader
		Ogre::MeshPtr mesh= Ogre::MeshManager::getSingleton().create(name, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true, loader);
		if (mesh.isNull()){
			OGRE_EXCEPT(Ogre::Exception::ERR_INVALID_STATE,
						"failed to create mesh object",
						"SceneObject::createMesh");
		}
		return mesh.getPointer();
	}

	Ogre::Skeleton* SceneObject::createSkeleton(const Ogre::String &name, Ogre::ManualResourceLoader *loader)
	{
		Ogre::SkeletonPtr skeleton= Ogre::SkeletonManager::getSingleton().create(name, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true, loader);
		if (skeleton.isNull()){
			OGRE_EXCEPT(Ogre::Exception::ERR_INVALID_STATE,
						"failed to create skeleton object",
						"SceneObject::createSkeleton");
		}
		return skeleton.getPointer();
	}

	void SceneObject::OnMouseMove(int x,int y)
	{
		if(_terrainManager){
			_terrainManager->OnMouseMove(x,y);
		}

		_mouseX=x;
		_mouseY=y;
	}

	void SceneObject::setPolyMode(Ogre::PolygonMode mode)
	{
		if(_camera){
			_camera->setPolygonMode(mode);
		}
	}

	void SceneObject::resetScreenResolution(int width,int height)
	{
		if(_window){
			_window->windowMovedOrResized();
		}
		if(_camera){
			_camera->setAspectRatio(width,height);
		}
	}


}
